SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
7 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
14.7 KB · 269 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { notFound } from "next/navigation";4import { Breadcrumbs } from "@/components/entity/breadcrumbs";5import { EntityMap } from "@/components/entity/entity-map";6import { JsonLd } from "@/components/entity/json-ld";7import { ExtLink, KeyValueList } from "@/components/entity/kv";8import { SectionNav } from "@/components/entity/section-nav";9import { SourcesFooter } from "@/components/entity/sources-footer";10import { EventList } from "@/components/event-list";11import { ProvenanceTable } from "@/components/entity/provenance";12import { FacilityTable } from "@/components/facility-table";13import { ApiNote, HonestEmpty } from "@/components/intelligence/shared";14import { Badge, PrecisionBadge } from "@/components/ui/badge";15import { Page, Section } from "@/components/ui/card";16import { CountryFlag } from "@/components/ui/country-flag";17import { Metric, MetricCell, MetricRow } from "@/components/ui/metric";18import { EmptyState, OfflineNotice } from "@/components/ui/states";19import { RowLink, TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table";20import { api } from "@/lib/api";21import { countryName, fmtDate, fmtInt } from "@/lib/format";22import { PRECISION_LABEL } from "@/lib/labels";23import { routes } from "@/lib/routes";24import { NOT_FOUND_METADATA, absoluteUrl, compactJsonLd, pageMetadata } from "@/lib/seo";25import { CLOUD_STATUS_LABEL, CloudStatusBadge, countryHrefMap } from "../cloud-format";2627export const revalidate = 300;2829type Params = Promise<{ slug: string }>;3031function describe(d: { provider: { name: string }; code: string; name: string; city: string | null; countryIso2: string | null; availabilityZones: number | null; launchedOn: string | null; status: string; isSovereign: boolean }): string {32  const parts = [`${d.provider.name} cloud region ${d.code} (${d.name})`];33  const place = [d.city, d.countryIso2 ? countryName(d.countryIso2) : null].filter(Boolean).join(", ");34  if (place) parts.push(`located in ${place}`);35  if (d.availabilityZones) parts.push(`${d.availabilityZones} availability ${d.availabilityZones === 1 ? "zone" : "zones"}`);36  if (d.launchedOn) parts.push(`launched ${fmtDate(d.launchedOn)}`);37  parts.push(`status ${CLOUD_STATUS_LABEL[d.status as keyof typeof CLOUD_STATUS_LABEL] ?? d.status}${d.isSovereign ? ", sovereign" : ""}`);38  return `${parts.join(" — ")}. Source-linked, city-level location.`;39}4041export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {42  const { slug } = await params;43  const r = await api.cloudRegion(slug);44  if (!r.ok) return r.status === 404 ? NOT_FOUND_METADATA : { title: "Cloud region" };45  const d = r.data;46  return pageMetadata({ title: `${d.provider.name} ${d.code} — ${d.name}`, description: describe(d), path: routes.cloudRegion(d.slug) });47}4849export default async function CloudRegionPage({ params }: { params: Params }) {50  const { slug } = await params;51  const [r, countryHref] = await Promise.all([api.cloudRegion(slug), countryHrefMap()]);52  if (!r.ok && r.status === 404) notFound();53  if (!r.ok) {54    return (55      <Page className="py-6">56        <Breadcrumbs items={[{ name: "Cloud regions", href: routes.cloudRegions() }, { name: slug, href: routes.cloudRegion(slug) }]} className="mb-4" />57        <OfflineNotice detail={r.error} />58      </Page>59    );60  }61  const d = r.data;62  const providerHref = `${routes.cloudRegions()}?provider=${encodeURIComponent(d.provider.slug)}`;63  const cHref = countryHref(d.countryIso2);64  const cName = d.countryName ?? (d.countryIso2 ? countryName(d.countryIso2) : null);65  const siblings = (d.siblingRegions ?? []).filter((s) => s.id !== d.id);66  const hosts = d.hostFacilities ?? [];67  const facilities = (d.marketFacilities?.length ? d.marketFacilities : d.facilitiesInMetro) ?? [];68  const events = d.events ?? [];69  const provenance = d.provenance ?? [];70  const ids = Object.entries(d.externalIds ?? {}).filter(([k]) => k !== "kind");71  const hasGeo = typeof d.lat === "number" && typeof d.lng === "number";7273  const jsonLd = compactJsonLd({74    "@context": "https://schema.org",75    "@type": "Place",76    name: `${d.provider.name} ${d.code} — ${d.name}`,77    url: absoluteUrl(routes.cloudRegion(d.slug)),78    description: describe(d),79    geo: hasGeo ? { "@type": "GeoCoordinates", latitude: d.lat, longitude: d.lng } : undefined,80    address: d.city || cName ? compactJsonLd({ "@type": "PostalAddress", addressLocality: d.city, addressCountry: d.countryIso2 }) : undefined,81    containedInPlace: cName ? { "@type": "Country", name: cName } : undefined,82    sameAs: d.sourceUrl ?? undefined,83  });8485  const nav = [86    { id: "location", label: "Location" },87    { id: "siblings", label: `Other ${d.provider.name} regions`, count: siblings.length },88    { id: "hosting", label: hosts.length ? "Host facilities" : "Hosting" },89    { id: "facilities", label: "Facilities in this market", count: facilities.length },90    { id: "events", label: "Events", count: events.length },91    { id: "provenance", label: "Provenance", count: provenance.length },92    { id: "identifiers", label: "Identifiers", count: ids.length },93    { id: "source", label: "Source" },94  ];9596  return (97    <Page>98      <JsonLd data={jsonLd} />99      <Breadcrumbs items={[{ name: "Cloud regions", href: routes.cloudRegions() }, { name: d.provider.name, href: providerHref }, { name: d.code, href: routes.cloudRegion(d.slug) }]} className="pt-4" />100101      <header className="py-4 sm:py-6">102        <div className="label-caps mb-1.5">103          <Link href={routes.operator(d.provider.slug)} className="hover:text-ink">104            {d.provider.name}105          </Link>{" "}106          · Cloud region107        </div>108        <h1 className="text-[22px] font-semibold leading-tight tracking-[-0.02em] text-ink sm:text-[28px]">{d.name}</h1>109        <div className="mt-2 flex flex-wrap items-center gap-2">110          <Badge tone="outline" mono>111            {d.code}112          </Badge>113          <CloudStatusBadge status={d.status} size="sm" />114          {d.isSovereign && <Badge tone="accent">Sovereign</Badge>}115          <PrecisionBadge precision={d.geoPrecision} />116        </div>117        <p className="mt-2 flex flex-wrap items-center gap-x-1.5 text-[13px] text-ink-2">118          {d.countryIso2 && <CountryFlag iso2={d.countryIso2} size={13} />}119          {d.city && <span>{d.city}</span>}120          {d.regionName && d.regionName !== d.city && <span className="text-ink-3">· {d.regionName}</span>}121          {cName && (122            <>123              <span className="text-ink-4">·</span>124              {cHref ? (125                <Link href={cHref} className="hover:text-ink hover:underline">126                  {cName}127                </Link>128              ) : (129                <span>{cName}</span>130              )}131            </>132          )}133          {d.metro && (134            <>135              <span className="text-ink-4">·</span>136              <Link href={routes.market(d.metro.slug)} className="hover:text-ink hover:underline">137                {d.metro.name} market138              </Link>139            </>140          )}141        </p>142        <p className="mt-1 text-[12px] text-ink-3">Location shown at {PRECISION_LABEL[d.geoPrecision].toLowerCase().replace(" location", "")} — cloud providers publish the metro, not the building.</p>143      </header>144145      <MetricRow cols={4}>146        <MetricCell>147          <Metric value={d.availabilityZones ?? "—"} label="Availability zones" note={d.availabilityZones === null ? "not published" : undefined} />148        </MetricCell>149        <MetricCell>150          <Metric value={fmtDate(d.launchedOn)} label="Launched" note={d.launchedOn ? undefined : "not published"} />151        </MetricCell>152        <MetricCell>153          <Metric value={fmtDate(d.announcedOn)} label="Announced" note={d.announcedOn ? undefined : "not published"} />154        </MetricCell>155        <MetricCell>156          <Metric value={fmtInt(siblings.length)} label={`Other ${d.provider.name} regions`} note={`${fmtInt(facilities.length)} indexed ${facilities.length === 1 ? "facility" : "facilities"} in this market`} />157        </MetricCell>158      </MetricRow>159160      <SectionNav items={nav} className="mt-4" />161162      <div className="mt-6 flex flex-col gap-8">163        <Section id="location" title="Location" description="Published location of the region; coordinates are city-level and never point at a physical data center." className="scroll-mt-28">164          <div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">165            <KeyValueList166              items={[167                { label: "City", value: d.city },168                { label: "Region", value: d.regionName },169                { label: "Country", value: cName ? cHref ? <Link href={cHref} className="hover:text-accent hover:underline">{cName}</Link> : cName : null },170                { label: "Market", value: d.metro ? <Link href={routes.market(d.metro.slug)} className="hover:text-accent hover:underline">{d.metro.name}</Link> : null },171                { label: "Coordinates", value: hasGeo ? <span className="figure">{d.lat!.toFixed(2)}, {d.lng!.toFixed(2)}</span> : null },172                { label: "Precision", value: PRECISION_LABEL[d.geoPrecision] },173              ]}174            />175            {hasGeo ? <EntityMap center={{ lat: d.lat!, lng: d.lng! }} precision="city" zoom={9} filters={d.countryIso2 ? { country: d.countryIso2 } : undefined} openLabel="Open market in map" className="h-[220px] md:h-full md:min-h-[260px]" /> : <EmptyState compact title="No published coordinates" description="The provider lists this region without a city we could place." />}176          </div>177        </Section>178179        {siblings.length > 0 && (180          <Section id="siblings" title={`Other ${d.provider.name} regions`} description={`${siblings.length} ${siblings.length === 1 ? "region" : "regions"} indexed for this provider`} className="scroll-mt-28" aside={<Link href={providerHref} className="text-[12px] text-ink-3 hover:text-ink">All {d.provider.name} regions</Link>}>181            <Table dense mobileCollapse={{ total: siblings.length, label: "regions" }}>182              <THead>183                <tr>184                  <Th>Code</Th>185                  <Th>Region</Th>186                  <Th className="hidden sm:table-cell">City</Th>187                  <Th>Country</Th>188                  <Th numeric className="hidden md:table-cell">189                    AZs190                  </Th>191                  <Th>Status</Th>192                </tr>193              </THead>194              <TBody>195                {siblings.map((s) => (196                  <Tr key={s.id}>197                    <Td primary>198                      <RowLink href={routes.cloudRegion(s.slug)} className="figure text-[12.5px]">199                        {s.code}200                      </RowLink>201                      <div className="text-[11.5px] text-ink-3 sm:hidden">{s.name}</div>202                    </Td>203                    <Td label="Region" className="hidden text-ink-2 sm:table-cell">204                      {s.name}205                    </Td>206                    <Td label="City" className="hidden text-ink-3 sm:table-cell">207                      {s.city ?? "—"}208                    </Td>209                    <Td label="Country">210                      <CountryFlag iso2={s.countryIso2} withName className="max-w-[160px] text-ink-2" />211                    </Td>212                    <Td label="AZs" numeric className="hidden md:table-cell">213                      {s.availabilityZones ?? <span className="text-ink-4">—</span>}214                    </Td>215                    <Td label="Status">216                      <CloudStatusBadge status={s.status} />217                    </Td>218                  </Tr>219                ))}220              </TBody>221            </Table>222          </Section>223        )}224225        <Section id="hosting" title={hosts.length ? "Host facilities (publicly verified)" : "Hosting"} description={hosts.length ? "Facilities a source publicly states host this region." : "Cloud providers publish the metro, not the building."} className="scroll-mt-28">226          {hosts.length ? (227            <FacilityTable items={hosts} columns={["operator", "country", "status", "mw"]} />228          ) : (229            <p className="text-[13.5px] text-ink-2">230              Region associated with {d.metro ? <>the <Link href={routes.market(d.metro.slug)} className="font-medium text-ink hover:text-accent hover:underline">{d.metro.name}</Link> market</> : cName ? <>a market in {cName}</> : <>a market</>} — not hosted at a specific facility.231            </p>232          )}233          <ApiNote note={d.note} className="mt-3" />234        </Section>235236        <Section id="facilities" title="Facilities in this market" description={d.metro ? `Indexed data centers in the ${d.metro.name} market — not necessarily hosting this region.` : "Indexed data centers around the published location — not necessarily hosting this region."} className="scroll-mt-28">237          {facilities.length ? <FacilityTable items={facilities} columns={["operator", "country", "status", "mw"]} /> : <HonestEmpty title="No indexed facility in this metro yet" detail="facilities appear as operator, registry and dataset connectors observe them" />}238        </Section>239240        <Section id="events" title="Events" description="Announcement, launch and update events detected for this region." className="scroll-mt-28">241          <EventList events={events} dense emptyTitle="No event recorded for this region" emptyDescription="Announcements and launches appear here when the provider's region list changes." />242        </Section>243244        <Section id="provenance" title="Provenance" description="Field by field: which source backs which value, with first / last observation." className="scroll-mt-28">245          <ProvenanceTable items={provenance} emptyDescription="No field-level provenance has been published for this region yet." />246        </Section>247248        {ids.length > 0 && (249          <Section id="identifiers" title="Identifiers" description="Provider identifiers as published." className="scroll-mt-28">250            <KeyValueList items={ids.map(([k, v]) => ({ label: k.replace(/_/g, " "), value: <span className="figure text-[12.5px]">{String(v)}</span> }))} columns={2} />251          </Section>252        )}253254        <Section id="source" title="Source" className="scroll-mt-28">255          {d.sourceUrl ? (256            <p className="text-[13px] text-ink-2">257              Provider documentation: <ExtLink href={d.sourceUrl} />258            </p>259          ) : (260            <p className="text-[13px] text-ink-3">No source URL recorded for this region.</p>261          )}262        </Section>263      </div>264265      <SourcesFooter sources={r.sources} updatedAt={d.updatedAt} note="Region facts are taken verbatim from the provider's published region list; availability-zone counts and launch dates are shown only when published." />266    </Page>267  );268}269